package com.fansl.allround.daemon.quartz.task;

import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.fansl.allround.common.core.constant.SecurityConstants;
import com.fansl.allround.common.core.constant.enums.RegionLevelEnum;
import com.fansl.allround.common.core.constant.enums.ScheduleJobStatusEnum;
import com.fansl.allround.common.core.constant.enums.ScheduleMisfirePolicyEnum;
import com.fansl.allround.daemon.quartz.config.ScheduleConstants;
import com.fansl.allround.daemon.quartz.dto.ScheduleJobDto;
import com.fansl.allround.daemon.quartz.service.ScheduleJobService;
import com.fansl.allround.upms.api.entity.SysRegion;
import com.fansl.allround.upms.api.feign.RemoteRegionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;
import us.codecraft.webmagic.selector.Selectable;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author fansl
 * @Description: 同步国家统计的区域信息
 * @date 2020/1/15 14:22
 */
@Slf4j
@Component
public class RegionSyncTask {
    @Autowired
    private RemoteRegionService remoteRegionService;
    @Autowired
    private ScheduleJobService scheduleJobService;

    private final Site site = Site.me()
            .setCharset("gbk").setTimeOut(100 * 1000)
            .setRetryTimes(3).setSleepTime(1000)
            .setCycleRetryTimes(3);

    /**
     * 同步省份
     *
     * @param needSyncNext
     */
    public void syncProvince(Boolean needSyncNext) {
        Spider.create(new PageProcessor() {
            @Override
            public void process(Page page) {
                Html html = page.getHtml();
                // 部分二：定义如何抽取页面信息，并保存下来
                List<Selectable> provinceHtmlList = html.xpath("//tr[@class='provincetr']/td/a").nodes();
                //省份信息
                List<SysRegion> provinceList = new ArrayList<>();
                List<String> provinceUrlList = new ArrayList<>();
                provinceHtmlList.stream().forEach(selectable -> {
                    String linkUrl = selectable.$("a", "href").get();
                    String code = StrUtil.fillAfter(StrUtil.removeAll(linkUrl, ".html"), "0".charAt(0), 12);
                    String pageUrl = page.getUrl().get();
                    linkUrl = ReUtil.replaceAll(pageUrl, "\\w+.html", linkUrl);
                    String name = selectable.$("a", "text").get();
                    log.info("省份行政编码：{}", code);
                    log.info("省份名称：{}", name);
                    log.info("省份下的链接：{}", linkUrl);
                    SysRegion province = new SysRegion();
                    province.setParentCode("CN")
                            .setName(name)
                            .setCode(code)
                            .setLevel(RegionLevelEnum.PROVINCE.getLevel())
                            .setNextLevelUrl(linkUrl);
                    province = setRegionAlias(province);
                    provinceList.add(province);
                    if (StrUtil.isNotBlank(linkUrl)) {
                        provinceUrlList.add(linkUrl);
                    }
                });
                remoteRegionService.saveBatch(provinceList, SecurityConstants.FROM_IN);
                if (needSyncNext) {
                    AtomicReference<LocalDateTime> cronDate = new AtomicReference<>(LocalDateTime.now());
                    provinceUrlList.stream().forEach(provinceUrl -> {
                        ScheduleJobDto scheduleJobDto = new ScheduleJobDto();
                        scheduleJobDto.setJobName(StrUtil.lowerFirst(StrUtil.lowerFirst(RegionSyncTask.class.getSimpleName())));
                        scheduleJobDto.setJobGroup(StrUtil.lowerFirst(RegionSyncTask.class.getSimpleName()));
                        scheduleJobDto.setMethodName("syncCity");

                        List<Map<String, String>> methodParamList = new ArrayList<>();
                        Map<String, String> methodParam = new HashMap<>();
                        methodParam.put(ScheduleConstants.METHOD_PARAM_TYPE_NAME, provinceUrl.getClass().getCanonicalName());
                        methodParam.put(ScheduleConstants.METHOD_PARAM_VALUE, provinceUrl);
                        methodParamList.add(methodParam);
                        methodParam = new HashMap<>();
                        methodParam.put(ScheduleConstants.METHOD_PARAM_TYPE_NAME, needSyncNext.getClass().getCanonicalName());
                        methodParam.put(ScheduleConstants.METHOD_PARAM_VALUE, needSyncNext.toString());
                        methodParamList.add(methodParam);
                        scheduleJobDto.setMethodParams(methodParamList);
                        cronDate.set(cronDate.get().plusMinutes(1));
                        String cronExp = String.format("%d %d %d %d %d ? %d",
                                cronDate.get().getSecond(), cronDate.get().getMinute(), cronDate.get().getHour(),
                                cronDate.get().getDayOfMonth(), cronDate.get().getMonthValue(), cronDate.get().getYear());
                        scheduleJobDto.setCronExpression(cronExp);
                        scheduleJobDto.setMisfirePolicy(ScheduleMisfirePolicyEnum.MISFIRE_DEFAULT);
                        scheduleJobDto.setConcurrent(true);
                        scheduleJobDto.setStatus(ScheduleJobStatusEnum.RUNNING);
                        scheduleJobDto.setRemark(String.format("同步%s下的%s信息", provinceUrl, "市区"));
                        scheduleJobService.saveJob(scheduleJobDto);
                    });
                }
            }

            @Override
            public Site getSite() {
                return site;
            }
        }).addUrl("http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2018/index.html")
                .thread(1)
                .run();
    }

    /**
     * 同步某个省下的城市
     *
     * @param provincePageUrl
     */
    public void syncCity(String provincePageUrl, Boolean needSyncNext) {
        //获取
        Spider.create(new PageProcessor() {

            @Override
            public void process(Page page) {
                String parentCode = StrUtil.fillAfter(StrUtil.subAfter(page.getUrl().toString(), "/", true)
                        .replace(".html", ""), "0".charAt(0), 12);
                log.info("处理parentCode为：{}的信息", parentCode);
                // 部分二：定义如何抽取页面信息，并保存下来
                Html html = page.getHtml();
                List<Selectable> cityHtmlList = html.xpath("//tr[@class='citytr']").nodes();

                // 部分三：从页面发现后续的url地址来抓取
                //市信息
                List<SysRegion> cityList = new ArrayList<>();
                List<String> cityUrlList = new ArrayList<>();
                cityHtmlList.stream().forEach(selectable -> {
                    //判断是否有链接
                    String linkUrl = selectable.links().get();
                    String code = linkUrl == null ?
                            selectable.$("td", "text").all().get(0) : selectable.$("td a", "text").all().get(0);
                    String name = linkUrl == null ?
                            selectable.$("td", "text").all().get(1) : selectable.$("td a", "text").all().get(1);
                    log.info("市行政编码：{}", code);
                    log.info("市名称：{}", name);
                    log.info("市下的链接：{}", linkUrl);
                    SysRegion city = new SysRegion();
                    city.setParentCode(parentCode)
                            .setName(name)
                            .setCode(code)
                            .setLevel(RegionLevelEnum.CITY.getLevel())
                            .setNextLevelUrl(linkUrl);
                    city = setRegionAlias(city);
                    cityList.add(city);
                    if (StrUtil.isNotBlank(linkUrl)) {
                        cityUrlList.add(linkUrl);
                    }
                });
                remoteRegionService.saveBatch(cityList, SecurityConstants.FROM_IN);
                if (needSyncNext) {
                    AtomicReference<LocalDateTime> cronDate = new AtomicReference<>(LocalDateTime.now());
                    cityUrlList.stream().forEach(cityUrl -> {
                        ScheduleJobDto scheduleJobDto = new ScheduleJobDto();
                        scheduleJobDto.setJobName(StrUtil.lowerFirst(RegionSyncTask.class.getSimpleName()));
                        scheduleJobDto.setJobGroup(StrUtil.lowerFirst(RegionSyncTask.class.getSimpleName()));
                        scheduleJobDto.setMethodName("syncCounty");

                        List<Map<String, String>> methodParamList = new ArrayList<>();
                        Map<String, String> methodParam = new HashMap<>();
                        methodParam.put(ScheduleConstants.METHOD_PARAM_TYPE_NAME, cityUrl.getClass().getCanonicalName());
                        methodParam.put(ScheduleConstants.METHOD_PARAM_VALUE, cityUrl);
                        methodParamList.add(methodParam);
                        methodParam = new HashMap<>();
                        methodParam.put(ScheduleConstants.METHOD_PARAM_TYPE_NAME, needSyncNext.getClass().getCanonicalName());
                        methodParam.put(ScheduleConstants.METHOD_PARAM_VALUE, needSyncNext.toString());
                        methodParamList.add(methodParam);
                        scheduleJobDto.setMethodParams(methodParamList);
                        cronDate.set(cronDate.get().plusMinutes(2));
                        String cronExp = String.format("%d %d %d %d %d ? %d",
                                cronDate.get().getSecond(), cronDate.get().getMinute(), cronDate.get().getHour(),
                                cronDate.get().getDayOfMonth(), cronDate.get().getMonthValue(), cronDate.get().getYear());
                        scheduleJobDto.setCronExpression(cronExp);
                        scheduleJobDto.setMisfirePolicy(ScheduleMisfirePolicyEnum.MISFIRE_DEFAULT);
                        scheduleJobDto.setConcurrent(true);
                        scheduleJobDto.setStatus(ScheduleJobStatusEnum.RUNNING);
                        scheduleJobDto.setRemark(String.format("同步%s下的%s信息", cityUrl, "区县"));
                        scheduleJobService.saveJob(scheduleJobDto);
                    });
                }
            }

            @Override
            public Site getSite() {
                return site;
            }
        })
                .addUrl(provincePageUrl)
                .thread(1)
                .run();
    }

    /**
     * 同步省市下的市区
     *
     * @param cityPageUrl
     * @param needSyncNext
     */
    public void syncCounty(String cityPageUrl, Boolean needSyncNext) {
        //获取
        Spider.create(new PageProcessor() {
            @Override
            public void process(Page page) {
                String parentCode = StrUtil.fillAfter(StrUtil.subAfter(page.getUrl().toString(), "/", true)
                        .replace(".html", ""), "0".charAt(0), 12);
                log.info("处理parentCode为：{}的信息", parentCode);
                // 部分二：定义如何抽取页面信息，并保存下来
                Html html = page.getHtml();
                List<Selectable> countyHtmlList = html.xpath("//tr[@class='countytr']").nodes();

                // 部分三：从页面发现后续的url地址来抓取
                //县信息
                List<SysRegion> countyList = new ArrayList<>();
                List<String> countyUrlList = new ArrayList<>();
                countyHtmlList.stream().forEach(selectable -> {
                    //判断是否有链接
                    String linkUrl = selectable.links().get();
                    String code = linkUrl == null ?
                            selectable.$("td", "text").all().get(0) : selectable.$("td a", "text").all().get(0);
                    String name = linkUrl == null ?
                            selectable.$("td", "text").all().get(1) : selectable.$("td a", "text").all().get(1);

                    log.info("县行政编码：{}", code);
                    log.info("县名称：{}", name);
                    log.info("县下的链接：{}", linkUrl);
                    SysRegion county = new SysRegion();
                    county.setParentCode(parentCode)
                            .setName(name)
                            .setCode(code)
                            .setLevel(RegionLevelEnum.COUNTY.getLevel())
                            .setNextLevelUrl(linkUrl);
                    county = setRegionAlias(county);
                    countyList.add(county);
                    if (StrUtil.isNotBlank(linkUrl)) {
                        countyUrlList.add(linkUrl);
                    }
                });
                remoteRegionService.saveBatch(countyList, SecurityConstants.FROM_IN);
                if (needSyncNext) {
                    AtomicReference<LocalDateTime> cronDate = new AtomicReference<>(LocalDateTime.now());
                    countyUrlList.stream().forEach(countyUrl -> {
                        ScheduleJobDto scheduleJobDto = new ScheduleJobDto();
                        scheduleJobDto.setJobName(StrUtil.lowerFirst(RegionSyncTask.class.getSimpleName()));
                        scheduleJobDto.setJobGroup(StrUtil.lowerFirst(RegionSyncTask.class.getSimpleName()));
                        scheduleJobDto.setMethodName("syncTown");

                        List<Map<String, String>> methodParamList = new ArrayList<>();
                        Map<String, String> methodParam = new HashMap<>();
                        methodParam.put(ScheduleConstants.METHOD_PARAM_TYPE_NAME, countyUrl.getClass().getCanonicalName());
                        methodParam.put(ScheduleConstants.METHOD_PARAM_VALUE, countyUrl);
                        methodParamList.add(methodParam);
                        methodParam = new HashMap<>();
                        methodParam.put(ScheduleConstants.METHOD_PARAM_TYPE_NAME, needSyncNext.getClass().getCanonicalName());
                        methodParam.put(ScheduleConstants.METHOD_PARAM_VALUE, needSyncNext.toString());
                        methodParamList.add(methodParam);
                        scheduleJobDto.setMethodParams(methodParamList);
                        cronDate.set(cronDate.get().plusMinutes(3));
                        String cronExp = String.format("%d %d %d %d %d ? %d",
                                cronDate.get().getSecond(), cronDate.get().getMinute(), cronDate.get().getHour(),
                                cronDate.get().getDayOfMonth(), cronDate.get().getMonthValue(), cronDate.get().getYear());
                        scheduleJobDto.setCronExpression(cronExp);
                        scheduleJobDto.setMisfirePolicy(ScheduleMisfirePolicyEnum.MISFIRE_DEFAULT);
                        scheduleJobDto.setConcurrent(true);
                        scheduleJobDto.setStatus(ScheduleJobStatusEnum.RUNNING);
                        scheduleJobDto.setRemark(String.format("同步%s下的%s信息", countyUrl, "城镇"));
                        scheduleJobService.saveJob(scheduleJobDto);
                    });
                }
            }

            @Override
            public Site getSite() {
                return site;
            }
        })
                .addUrl(cityPageUrl)
                .thread(1)
                .run();
    }

    /**
     * 同步区县下的城镇
     *
     * @param countyPageUrl
     * @param needSyncNext
     */
    public void syncTown(String countyPageUrl, Boolean needSyncNext) {
        //获取
        Spider.create(new PageProcessor() {

            @Override
            public void process(Page page) {
                String parentCode = StrUtil.fillAfter(StrUtil.subAfter(page.getUrl().toString(), "/", true)
                        .replace(".html", ""), "0".charAt(0), 12);
                log.info("处理parentCode为：{}的信息", parentCode);
                // 部分二：定义如何抽取页面信息，并保存下来
                Html html = page.getHtml();
                List<Selectable> townHtmlList = html.xpath("//tr[@class='towntr']").nodes();

                // 部分三：从页面发现后续的url地址来抓取
                //城镇信息
                List<SysRegion> townList = new ArrayList<>();
                List<String> townUrlList = new ArrayList<>();
                townHtmlList.stream().forEach(selectable -> {
                    //判断是否有链接
                    String linkUrl = selectable.links().get();
                    String code = linkUrl == null ?
                            selectable.$("td", "text").all().get(0) : selectable.$("td a", "text").all().get(0);
                    String name = linkUrl == null ?
                            selectable.$("td", "text").all().get(1) : selectable.$("td a", "text").all().get(1);
                    log.info("城镇行政编码：{}", code);
                    log.info("城镇名称：{}", name);
                    log.info("城镇下的链接：{}", linkUrl);
                    SysRegion town = new SysRegion();
                    town.setParentCode(parentCode)
                            .setName(name)
                            .setCode(code)
                            .setLevel(RegionLevelEnum.TOWN.getLevel())
                            .setNextLevelUrl(linkUrl);
                    town = setRegionAlias(town);
                    townList.add(town);
                    if (StrUtil.isNotBlank(linkUrl)) {
                        townUrlList.add(linkUrl);
                    }
                });
                remoteRegionService.saveBatch(townList, SecurityConstants.FROM_IN);
                if (needSyncNext) {
                    AtomicReference<LocalDateTime> cronDate = new AtomicReference<>(LocalDateTime.now());
                    townUrlList.stream().forEach(townUrl -> {
                        ScheduleJobDto scheduleJobDto = new ScheduleJobDto();
                        scheduleJobDto.setJobName(StrUtil.lowerFirst(RegionSyncTask.class.getSimpleName()));
                        scheduleJobDto.setJobGroup(StrUtil.lowerFirst(RegionSyncTask.class.getSimpleName()));
                        scheduleJobDto.setMethodName("syncVillage");

                        List<Map<String, String>> methodParamList = new ArrayList<>();
                        Map<String, String> methodParam = new HashMap<>();
                        methodParam.put(ScheduleConstants.METHOD_PARAM_TYPE_NAME, townUrl.getClass().getCanonicalName());
                        methodParam.put(ScheduleConstants.METHOD_PARAM_VALUE, townUrl);
                        methodParamList.add(methodParam);
                        scheduleJobDto.setMethodParams(methodParamList);
                        cronDate.set(cronDate.get().plusMinutes(4));
                        String cronExp = String.format("%d %d %d %d %d ? %d",
                                cronDate.get().getSecond(), cronDate.get().getMinute(), cronDate.get().getHour(),
                                cronDate.get().getDayOfMonth(), cronDate.get().getMonthValue(), cronDate.get().getYear());
                        scheduleJobDto.setCronExpression(cronExp);
                        scheduleJobDto.setMisfirePolicy(ScheduleMisfirePolicyEnum.MISFIRE_DEFAULT);
                        scheduleJobDto.setConcurrent(true);
                        scheduleJobDto.setStatus(ScheduleJobStatusEnum.RUNNING);
                        scheduleJobDto.setRemark(String.format("同步%s下的%s信息", townUrl, "行政村"));
                        scheduleJobService.saveJob(scheduleJobDto);
                    });
                }
            }

            @Override
            public Site getSite() {
                return site;
            }
        })
                .addUrl(countyPageUrl)
                .thread(1)
                .run();
    }

    /**
     * 同步城镇下的行政村
     *
     * @param townPageUrl
     */
    public void syncVillage(String townPageUrl) {
        //获取
        Spider.create(new PageProcessor() {
            @Override
            public void process(Page page) {
                String parentCode = StrUtil.fillAfter(StrUtil.subAfter(page.getUrl().toString(), "/", true)
                        .replace(".html", ""), "0".charAt(0), 12);
                log.info("处理parentCode为：{}的信息", parentCode);
                // 部分二：定义如何抽取页面信息，并保存下来
                Html html = page.getHtml();
                List<Selectable> villageHtmlList = html.xpath("//tr[@class='villagetr']").nodes();

                // 部分三：从页面发现后续的url地址来抓取
                //村信息
                List<SysRegion> villageList = new ArrayList<>();
                List<String> villageUrlList = new ArrayList<>();
                villageHtmlList.stream().forEach(selectable -> {
                    //判断是否有链接
                    String linkUrl = selectable.links().get();
                    String code = linkUrl == null ?
                            selectable.$("td", "text").all().get(0) : selectable.$("td a", "text").all().get(0);
                    String name = linkUrl == null ?
                            selectable.$("td", "text").all().get(2) : selectable.$("td a", "text").all().get(2);
                    log.info("村行政编码：{}", code);
                    log.info("村名称：{}", name);
                    log.info("村下的链接：{}", linkUrl);
                    SysRegion village = new SysRegion();
                    village.setParentCode(parentCode)
                            .setName(name)
                            .setCode(code)
                            .setLevel(RegionLevelEnum.VILLAGE.getLevel())
                            .setNextLevelUrl(linkUrl);
                    village = setRegionAlias(village);
                    villageList.add(village);
                    if (StrUtil.isNotBlank(linkUrl)) {
                        villageUrlList.add(linkUrl);
                    }
                });
                remoteRegionService.saveBatch(villageList, SecurityConstants.FROM_IN);
            }

            @Override
            public Site getSite() {
                return site;
            }
        })
                .addUrl(townPageUrl)
                .thread(1)
                .run();
    }

    private SysRegion setRegionAlias(SysRegion sysRegion) {
        List<String> removeStrList = Arrays.asList("省", "市", "办事处", "居委会", "行政村", "村委会", "村民委员会", "居民委员会");
        removeStrList.forEach(removeStr -> {
            if (StrUtil.isBlank(sysRegion.getSimpleName())) {
                if (StrUtil.endWith(sysRegion.getName(), removeStr)) {
                    sysRegion.setSimpleName(StrUtil.removeSuffix(sysRegion.getName(), removeStr));
                }
            }
            if (StrUtil.isBlank(sysRegion.getAlias())) {
                if (StrUtil.endWith(sysRegion.getName(), removeStr)) {
                    sysRegion.setAlias(StrUtil.removeSuffix(sysRegion.getName(), removeStr));
                }
            }
        });
        if (StrUtil.isBlank(sysRegion.getSimpleName())) {
            sysRegion.setSimpleName(sysRegion.getName());
        }
        if (StrUtil.isBlank(sysRegion.getAlias())) {
            sysRegion.setAlias(sysRegion.getName());
        }
        return sysRegion;
    }
}
